﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using OnlinePlots.Plots.Elements;
using OnlinePlots.Plots.DataSets;
using OnlinePlots.Plots.Scales;
using System.Windows.Browser;
using System.Globalization;
using OnlinePlots.Plots;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;

namespace OnlinePlotsService
{
    public partial class Page : UserControl
    {
        private ObservableCollection<FunctionItem> _functions = new ObservableCollection<FunctionItem>();

        public Page()
        {
            InitializeComponent();
            dgFunctions.ItemsSource = _functions;

            App.Current.Host.Content.FullScreenChanged += new EventHandler(Content_FullScreenChanged);
            this.KeyDown += new KeyEventHandler(Page_KeyDown);

            RefreshZoomSliderValue();
        }

        void Page_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Add:
                case Key.PageUp:
                    explicitFunctionPlotControl.ZoomLogicalFrame(1 / explicitFunctionPlotControl.MouseZoomFactor);
                    explicitFunctionPlotControl.Redraw();
                    break;
                case Key.Subtract:
                case Key.PageDown:
                    explicitFunctionPlotControl.ZoomLogicalFrame(explicitFunctionPlotControl.MouseZoomFactor);
                    explicitFunctionPlotControl.Redraw();
                    break;
                case Key.Left:
                    explicitFunctionPlotControl.MoveLogicalFrame(-explicitFunctionPlotControl.LogicalFrameWidth / 2, 0);
                    explicitFunctionPlotControl.Redraw();
                    break;
                case Key.Right:
                    explicitFunctionPlotControl.MoveLogicalFrame(explicitFunctionPlotControl.LogicalFrameWidth / 2, 0);
                    explicitFunctionPlotControl.Redraw();
                    break;
                case Key.Down:
                    explicitFunctionPlotControl.MoveLogicalFrame(0, -explicitFunctionPlotControl.LogicalFrameHeight / 2);
                    explicitFunctionPlotControl.Redraw();
                    break;
                case Key.Up:
                    explicitFunctionPlotControl.MoveLogicalFrame(0, explicitFunctionPlotControl.LogicalFrameHeight / 2);
                    explicitFunctionPlotControl.Redraw();
                    break;
            }
        }

        void function_MathCompilerExceptionRaised(object sender, Exception e)
        {
            HtmlPage.Window.Dispatcher.BeginInvoke(delegate
            {
                HtmlPage.Window.Alert(string.Format("Syntax error:\n {0}\n\nPlease, correct function definition.", e.Message));
            });
        }

        void function_VisibilityChanged(object sender, EventArgs e)
        {
            explicitFunctionPlotControl.Redraw();
        }

        private void ApplyFunction()
        {
            FunctionItem f = (FunctionItem)dgFunctions.SelectedItem;
            try
            {
                f.Function = tbFunction.Text;
            } 
            catch(Exception e)
            {
                f.Function = "0";
                HtmlPage.Window.Dispatcher.BeginInvoke(delegate
                {
                    HtmlPage.Window.Alert(string.Format("Syntax error:\n {0}\n\nPlease, correct function definition.", e.Message));
                });
            }
        }

        private void ApplyButton_Click(object sender, RoutedEventArgs e)
        {
            ApplyFunction();
            explicitFunctionPlotControl.Redraw();
        }

        private void FunctionTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                ApplyFunction();
                explicitFunctionPlotControl.Redraw();
            }
        }

        private void dgFunctions_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (dgFunctions.SelectedIndex == -1)
            {
                tbFunction.Text = String.Empty;

                bDelete.IsEnabled = false;
                tbFunction.IsEnabled = false;
                bApply.IsEnabled = false;
            }
            else
            {
                FunctionItem f = (FunctionItem)dgFunctions.SelectedItem;
                tbFunction.Text = f.Function;

                bDelete.IsEnabled = true;
                tbFunction.IsEnabled = true;
                bApply.IsEnabled = true;
            }
        }

        private void dgFunctions_CurrentCellChanged(object sender, EventArgs e)
        {
            if (dgFunctions.CurrentColumn.DisplayIndex == 1)
            {
                explicitFunctionPlotControl.Redraw();
            }
        }

        /// <summary>
        /// New function.
        /// </summary>
        private void AddNewFunction()
        {
            FunctionItem function = new FunctionItem();
            function.MathCompilerExceptionRaised += new MathCompilerExceptionRaisedEventHandler(function_MathCompilerExceptionRaised);
            function.VisibilityChanged += new EventHandler(function_VisibilityChanged);
            _functions.Add(function);

            explicitFunctionPlotControl.Items.Add(function.PlotItem);
            explicitFunctionPlotControl.Redraw();

            dgFunctions.SelectedIndex = _functions.Count - 1;

            if (_functions.Count == 10) bAdd.IsEnabled = false;
            bDelete.IsEnabled = true;
            bSave.IsEnabled = true;
        }

        private void SavePlots(object state)
        {
            try
            {
                string data = (string)state;

                WebRequest webRequest = WebRequest.Create(new Uri(App.SAVE_PLOTS_SCRIPT));
                webRequest.Method = "POST";
                webRequest.ContentType = "application/x-www-form-urlencoded";

                AutoResetEvent operationComplete = new AutoResetEvent(false);
                IAsyncResult result = webRequest.BeginGetRequestStream(x => operationComplete.Set(), null);
                operationComplete.WaitOne();

                using (var requestStream = webRequest.EndGetRequestStream(result))
                {
                    byte[] postData = Encoding.UTF8.GetBytes(string.Format("data={0}", HttpUtility.UrlEncode(data)));
                    requestStream.Write(postData, 0, postData.Length);
                    requestStream.Close();

                    result = webRequest.BeginGetResponse(x => operationComplete.Set(), null);
                }

                operationComplete.WaitOne();
                using (var webResponse = webRequest.EndGetResponse(result))
                {
                    Stream responseStream = webResponse.GetResponseStream();
                    XmlReader reader = XmlReader.Create(responseStream);
                    while (reader.Read())
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            if (reader.Name != "data") throw new Exception(); // TODO: бросить что-то разумное

                            if (reader.GetAttribute("status") == "0")
                            {
                                string filename = reader.GetAttribute("file");
                                Dispatcher.BeginInvoke(() => { HtmlPage.Window.Navigate(new Uri(string.Format("{0}?file={1}", App.SAVE_PLOTS_SCRIPT, filename))); });
                            }
                        }
                    }
                }
            }
            catch
            {
                Dispatcher.BeginInvoke(() => { HtmlPage.Window.Alert("An error occured while saving. Please, try again later."); });
            }
            finally
            {
                Dispatcher.BeginInvoke(() => { bSave.IsEnabled = true; Cursor = Cursors.Arrow; });
            }
        }

        #region Event Handlers (add, delete etc)

        private void bAdd_Click(object sender, RoutedEventArgs e)
        {
            AddNewFunction();
        }

        private void bDelete_Click(object sender, RoutedEventArgs e)
        {
            if (dgFunctions.SelectedIndex == -1) return;

            FunctionItem function = (FunctionItem)dgFunctions.SelectedItem;
            _functions.Remove(function);

            explicitFunctionPlotControl.Items.Remove(function.PlotItem);
            explicitFunctionPlotControl.Redraw();

            bAdd.IsEnabled = true;
            if (_functions.Count == 0) bSave.IsEnabled = false;
        }

        /// <summary>
        /// Открытие файла.
        /// </summary>
        private void bOpen_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog fd = new OpenFileDialog();
            fd.Filter = "Plot Files (.xplt)|*.xplt|All Files (*.*)|*.*";
            fd.FilterIndex = 1;
            if (fd.ShowDialog() == true)
            {
                dgFunctions.SelectedIndex = -1;
                dgFunctions.ItemsSource = null;
                explicitFunctionPlotControl.Items.Clear();

                XmlSerializer xmlSerializer = new XmlSerializer(_functions.GetType());
                _functions = (ObservableCollection<FunctionItem>)xmlSerializer.Deserialize(fd.File.OpenRead());

                foreach (var function in _functions)
                {
                    function.VisibilityChanged += new EventHandler(function_VisibilityChanged);
                    function.MathCompilerExceptionRaised += new MathCompilerExceptionRaisedEventHandler(function_MathCompilerExceptionRaised);
                    explicitFunctionPlotControl.Items.Add(function.PlotItem);
                }

                dgFunctions.ItemsSource = _functions;
                explicitFunctionPlotControl.Redraw();
            }
        }

        /// <summary>
        /// Сохранение файла.
        /// </summary>
        private void bSave_Click(object sender, RoutedEventArgs e)
        {
            bSave.IsEnabled = false;
            Cursor = Cursors.Wait;

            MemoryStream ms = new MemoryStream();
            XmlSerializer xmlSerializer = new XmlSerializer(_functions.GetType());
            xmlSerializer.Serialize(ms, _functions);

            Thread saveThread = new Thread(new ParameterizedThreadStart(SavePlots));
            saveThread.Start(Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length));
        }

        #endregion

        #region Open/close help toolbar

        private void bExpander_Click(object sender, RoutedEventArgs e)
        {
            if (cHelp.MinWidth == cHelp.MaxWidth)
            {
                doubleAnimation.To = cHelp.Width.Value;
                pHelp.Visibility = Visibility.Visible;
            }
            else
            {
                doubleAnimation.To = cHelp.MinWidth;
            }

            columnAnimation.Begin();
        }

        private void columnAnimation_Completed(object sender, EventArgs e)
        {
            if (cHelp.MaxWidth == cHelp.MinWidth)
            {
                pHelp.Visibility = Visibility.Collapsed;
            }
        }

        #endregion

        #region Functions examples

        private void lSampleFunc1_Click(object sender, RoutedEventArgs e)
        {
            if (_functions.Count == 0)
            {
                AddNewFunction();
            }

            tbFunction.Text = "1 / x";
            ApplyFunction();

            explicitFunctionPlotControl.Redraw();
        }

        private void lSampleFunc2_Click(object sender, RoutedEventArgs e)
        {
            if (_functions.Count == 0)
            {
                AddNewFunction();
            }

            tbFunction.Text = "log(x)";
            ApplyFunction();

            explicitFunctionPlotControl.Redraw();
        }

        private void lSampleFunc3_Click(object sender, RoutedEventArgs e)
        {
            if (_functions.Count == 0)
            {
                AddNewFunction();
            }

            tbFunction.Text = "sqrt(x)";
            ApplyFunction();

            explicitFunctionPlotControl.Redraw();
        }

        private void lSampleFunc4_Click(object sender, RoutedEventArgs e)
        {
            if (_functions.Count == 0)
            {
                AddNewFunction();
            }

            tbFunction.Text = "x * sin(x)";
            ApplyFunction();

            explicitFunctionPlotControl.Redraw();
        }

        private void lSampleFunc5_Click(object sender, RoutedEventArgs e)
        {
            if (_functions.Count == 0)
            {
                AddNewFunction();
            }

            tbFunction.Text = "x - 2 ? tg(x) : x";
            ApplyFunction();

            explicitFunctionPlotControl.Redraw();
        }

        #endregion

        #region Color choice

        private void bSelectColor_Click(object sender, RoutedEventArgs e)
        {
            if (colorPicker.Visibility == Visibility.Collapsed)
            {
                FunctionItem f = (FunctionItem)((DataGridCell)((Button)e.OriginalSource).Parent).DataContext;
                colorPicker.SelectedColor = f.Color;
                colorPicker.Tag = f;
                colorPicker.Margin = new Thickness(0, dgFunctions.Margin.Top + ((DataGridCell)((Button)e.OriginalSource).Parent).ActualHeight * (_functions.IndexOf(f) + 2), 0, 0);

                colorPicker.Visibility = Visibility.Visible;
            }
            else
            {
                colorPicker.Visibility = Visibility.Collapsed;
            }
        }

        private void colorPicker_Clicked(object sender, EventArgs e)
        {
            colorPicker.Visibility = Visibility.Collapsed;
        }

        private void colorPicker_ColorChanged(object sender, EventArgs e)
        {
            if (colorPicker.Visibility == Visibility.Visible)
            {
                FunctionItem f = (FunctionItem)colorPicker.Tag;
                f.Color = colorPicker.SelectedColor;
                explicitFunctionPlotControl.Redraw();
            }
        }

        #endregion

        #region Toolbar

        /// <summary>
        /// Полноэкранный режим.
        /// </summary>
        private void bFullScreen_Click(object sender, RoutedEventArgs e)
        {
            App.Current.Host.Content.IsFullScreen = !App.Current.Host.Content.IsFullScreen;
        }

        void Content_FullScreenChanged(object sender, EventArgs e)
        {
            if (App.Current.Host.Content.IsFullScreen)
            {
                LayoutRoot.RowDefinitions[1].MaxHeight = 0;
                LayoutRoot.ColumnDefinitions[1].MaxWidth = 0;

                cHelp.MinWidth = 0;
                cHelp.MaxWidth = 0;

                bFullScreen.ContentTemplate = (DataTemplate)Resources["UnfullscreenButtonTemplate"];
            }
            else
            {
                App.Current.Host.Content.IsFullScreen = false;
                LayoutRoot.RowDefinitions[1].MaxHeight = LayoutRoot.RowDefinitions[1].Height.Value;
                LayoutRoot.ColumnDefinitions[1].MaxWidth = LayoutRoot.ColumnDefinitions[1].Width.Value;

                cHelp.MinWidth = 35;
                cHelp.MaxWidth = pHelp.Visibility == Visibility.Collapsed ? cHelp.MinWidth : cHelp.MaxWidth = cHelp.Width.Value;

                bFullScreen.ContentTemplate = (DataTemplate)Resources["FullscreenButtonTemplate"];
            }
        }

        /// <summary>
        /// Установка начала координат в центре.
        /// </summary>
        private void bCentre_Click(object sender, RoutedEventArgs e)
        {
            explicitFunctionPlotControl.CenterLogicalFrameAtPoint(0, 0);
            explicitFunctionPlotControl.LogicalFrameXMin = -5;
            explicitFunctionPlotControl.LogicalFrameXMax = 5;
            explicitFunctionPlotControl.LogicalFrameYMin = -5;
            explicitFunctionPlotControl.LogicalFrameYMax = 5;
            explicitFunctionPlotControl.Redraw();

            RefreshZoomSliderValue();
        }

        /// <summary>
        /// Приближение/удаление графика.
        /// </summary>
        private void sZoom_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (explicitFunctionPlotControl == null) return;

            double v = Math.Pow(10, e.NewValue);
            if (Math.Abs(v - explicitFunctionPlotControl.LogicalFrameWidth) > 10E-6)
            {
                explicitFunctionPlotControl.ZoomLogicalFrame(v / explicitFunctionPlotControl.LogicalFrameWidth);
                explicitFunctionPlotControl.Redraw();
            }
        }

        private void RefreshZoomSliderValue()
        {
            double v = Math.Log10(explicitFunctionPlotControl.LogicalFrameWidth);
            if (v != sZoom.Value)
            {
                sZoom.Value = v;
            }
        }

        private void explicitFunctionPlotControl_LogicalFrameZoomed(object sender, EventArgs e)
        {
            RefreshZoomSliderValue();

            bZoomIn.IsEnabled = sZoom.Value != sZoom.Minimum;
            bZoomOut.IsEnabled = sZoom.Value != sZoom.Maximum;
        }

        private void bZoomOut_Click(object sender, RoutedEventArgs e)
        {
            explicitFunctionPlotControl.ZoomLogicalFrame(explicitFunctionPlotControl.MouseZoomFactor);
            explicitFunctionPlotControl.Redraw();
        }

        private void bZoomIn_Click(object sender, RoutedEventArgs e)
        {
            explicitFunctionPlotControl.ZoomLogicalFrame(1 / explicitFunctionPlotControl.MouseZoomFactor);
            explicitFunctionPlotControl.Redraw();
        }

        #endregion
    }
}
